﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using Microsoft.SqlServer.Dts.Pipeline;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Pipeline.Design;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Design;

/********************************************************************************************/
/* Component Name : Error Output's Description                                              */
/* Component Type : Dataflow Tranform Component                                             */
/* Description : This dataflow component provides information on one or many error outputs. */
/*                                                                                          */
/* How does this component work :                                                           */
/*   - Design Time                                                                          */
/*      Retrieve and store Lineage ID of all columns of the current dataflow.               */
/*      Retrieve user's preferences.                                                        */
/*      Create output columns.                                                              */
/*   - Running Time                                                                         */
/*      Foreach input line, search for the Lineage ID which generates the error.            */
/*      Write output with information on this column.                                       */
/*                                                                                          */
/* Author : Rémi PESTRE                                                                     */
/* Creation date : 12/02/2009                                                               */
/********************************************************************************************/

namespace Pestre.SqlServer.SSIS.DataFlowComponent
{
    /*
     * Main class for the component "Error Output's Description".
     * This dataflow component provides information on one or many error outputs.
     * 
     * This class is composed by three kinds of methods
     *      - Methods for Design Time
     *      - Methods for Design and Running Time
     *      - Methods for Running Time
     */
    [DtsPipelineComponent(
        // This name will be displayed in BIDS.
        DisplayName = "Error Description",
        // Define the class which owns the User Interface.
        UITypeName = "Pestre.SqlServer.SSIS.DataFlowComponent.ErrorOutputDescriptionUI90,ErrorOutputDescription,Version=1.0.0.0,Culture=neutral,PublicKeyToken=97ab4c916d7328ad",
        // Choose component type : This is a transformation component.
        ComponentType = ComponentType.Transform,
        // Display our icon which is embedded in the dll file.
        IconResource = "Pestre.SqlServer.SSIS.DataFlowComponent.ErrorOutputDescription90.ico")]
    public class ErrorOutputDescription : PipelineComponent
    {
        // Name of output's columns generated by this component
        public const string EOD_ERROR_CODE        = "EOD_ErrorCode";
        public const string EOD_ERROR_COLUMN      = "EOD_ErrorColumn";
        public const string EOD_ERROR_DESCRIPTION = "EOD_ErrorDescription";
        public const string EOD_COMPONENT_NAME    = "EOD_ComponentName";
        public const string EOD_COLUMN_NAME       = "EOD_ColumnName";
        public const string EOD_COLUMN_TYPE       = "EOD_ColumnType";
        public const string EOD_COLUMN_LENGTH     = "EOD_ColumnLengh";

        // Name of custom properties (User preferences and System information will be stored in these properties)
        public const string PROP_LINEAGE_ID        = "Input Lineage ID";
        public const string PROP_VIRTUAL_COLUMNS   = "Virtual Input Columns";
        public const string PROP_SHOW_WARNING      = "Show warning";
        public const string PROP_ERROR_CODE        = "Add Error Code";
        public const string PROP_ERROR_COLUMN      = "Add Error Column";
        public const string PROP_ERROR_DESCRIPTION = "Add Error Description";
        public const string PROP_COMPONENT_NAME    = "Add Component Name";
        public const string PROP_COLUMN_NAME       = "Add Column Name";
        public const string PROP_COLUMN_TYPE       = "Add Column Type";
        public const string PROP_COLUMN_LENGTH     = "Add Column Length";

        // Custom Properties of the component
        private IDTSCustomProperty90 _cpLineage;
        private IDTSCustomProperty90 _cpVirtualColumns;
        private IDTSCustomProperty90 _cpShowWarning;
        private IDTSCustomProperty90 _cpAddErrorCode;
        private IDTSCustomProperty90 _cpAddErrorColumn;
        private IDTSCustomProperty90 _cpAddErrorDescription;
        private IDTSCustomProperty90 _cpAddComponentName;
        private IDTSCustomProperty90 _cpAddColumnName;
        private IDTSCustomProperty90 _cpAddColumnType;
        private IDTSCustomProperty90 _cpAddColumnLength;
        private ColumnInfo90[]         _ciColumnInfo;
        private VirtualInputColumns90 _VirtualInputColumns; // All virtual input columns

        // Runtime properties : These attributes are initiliaze during PreExecute phase.
        private int[] _ErrorCodeIndex;       // Index of "Error Code" input columns
        private int[] _ErrorColumnIndex;     // Index of "Error Column" input columns
        private int _iErrorCodeIndex;        // Index of "EOD_ErrorCode" output column
        private int _iErrorColumnIndex;      // Index of "EOD_ErrorColumn" output column
        private int _iErrorDescriptionIndex; // Index of "EOD_ErrorDescription" output column
        private int _iComponentNameIndex;    // Index of "EOD_ComponentName" output column
        private int _iColumnNameIndex;       // Index of "EOD_ColumnName" output column
        private int _iColumnTypeIndex;       // Index of "EOD_ColumnType" output column
        private int _iColumnLengthIndex;     // Index of "EOD_ColumnLengh" output column
        private PipelineBuffer _outputBuffer; // Output Buffer (This component has only one output)
        private int _iNbActiveInput; // Number of active inputs (Used to determine when to close output flow)
        private int _iNbGeneratedRows; // Number of rows generated by this component

        /*******************************************************************************************/
        /*  DESIGN TIME : The following methods are called during design time.                     */
        /*     - ProvideComponentProperties() : Initialize component's properties                  */
        /*     - OnInputPathAttached() : Called when a new path is attached to the component       */
        /*     - OnInputPathDetached() : Called when an existing path is removed                   */
        /*******************************************************************************************/

        /*
         * Initialize component's properties and create Input and Output.
         *   - Custom properties are used to store information. It's the only way to keep 
         *     information between design time and running time.
         *   - The first input is created to allow user to attach an error output.
         *   - The component has only one output which is created here. (Asynchronous ouput)
         */
        public override void ProvideComponentProperties()
        {
            // Call base method which creates one input and one output
            base.ProvideComponentProperties();

            // Rename input and output added by the base class.
            ComponentMetaData.InputCollection[0].Name  = "Error Input 0";
            ComponentMetaData.InputCollection[0].Dangling = false; // Can not be unattached
            ComponentMetaData.OutputCollection[0].Name = "Error Description Output";
            ComponentMetaData.OutputCollection[0].SynchronousInputID = 0; // Asynchronous output

            // Create custom properties to store user perferences and system information.
            // Every option selected by the user in User Interface will be stored in these properties.
            _cpLineage             = ComponentMetaData.CustomPropertyCollection.New();
            _cpVirtualColumns      = ComponentMetaData.CustomPropertyCollection.New();
            _cpShowWarning         = ComponentMetaData.CustomPropertyCollection.New();
            _cpAddErrorCode        = ComponentMetaData.CustomPropertyCollection.New();
            _cpAddErrorColumn      = ComponentMetaData.CustomPropertyCollection.New();
            _cpAddErrorDescription = ComponentMetaData.CustomPropertyCollection.New();
            _cpAddComponentName    = ComponentMetaData.CustomPropertyCollection.New();
            _cpAddColumnName       = ComponentMetaData.CustomPropertyCollection.New();
            _cpAddColumnType       = ComponentMetaData.CustomPropertyCollection.New();
            _cpAddColumnLength     = ComponentMetaData.CustomPropertyCollection.New();
            _cpLineage.Name             = PROP_LINEAGE_ID;
            _cpVirtualColumns.Name      = PROP_VIRTUAL_COLUMNS;
            _cpShowWarning.Name         = PROP_SHOW_WARNING;
            _cpAddErrorCode.Name        = PROP_ERROR_CODE;
            _cpAddErrorColumn.Name      = PROP_ERROR_COLUMN;
            _cpAddErrorDescription.Name = PROP_ERROR_DESCRIPTION;
            _cpAddComponentName.Name    = PROP_COMPONENT_NAME;
            _cpAddColumnName.Name       = PROP_COLUMN_NAME;
            _cpAddColumnType.Name       = PROP_COLUMN_TYPE;
            _cpAddColumnLength.Name     = PROP_COLUMN_LENGTH;
            _cpLineage.Value             = "";
            _cpVirtualColumns.Value      = "";
            _cpShowWarning.Value         = true;
            _cpAddErrorCode.Value        = true;
            _cpAddErrorColumn.Value      = true;
            _cpAddErrorDescription.Value = true;
            _cpAddComponentName.Value    = true;
            _cpAddColumnName.Value       = true;
            _cpAddColumnType.Value       = false;
            _cpAddColumnLength.Value     = false;

            //Create initial output columns
            if ((bool)_cpAddErrorCode.Value == true)
            {
                IDTSOutputColumn90 outErrorCode = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                outErrorCode.Name = ErrorOutputDescription.EOD_ERROR_CODE;
                outErrorCode.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I4, 0, 0, 0, 0);
            }
            if ((bool)_cpAddErrorColumn.Value == true)
            {
                IDTSOutputColumn90 outErrorColumn = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                outErrorColumn.Name = ErrorOutputDescription.EOD_ERROR_COLUMN;
                outErrorColumn.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I4, 0, 0, 0, 0);
            }
            if ((bool)_cpAddErrorDescription.Value == true)
            {
                IDTSOutputColumn90 outDescription = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                outDescription.Name = ErrorOutputDescription.EOD_ERROR_DESCRIPTION;
                outDescription.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_STR, 500, 0, 0, 1252);
            }
            if ((bool)_cpAddComponentName.Value == true)
            {
                IDTSOutputColumn90 outComponent = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                outComponent.Name = ErrorOutputDescription.EOD_COMPONENT_NAME;
                outComponent.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_STR, 100, 0, 0, 1252);
            }
            if ((bool)_cpAddColumnName.Value == true)
            {
                IDTSOutputColumn90 outName = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                outName.Name = ErrorOutputDescription.EOD_COLUMN_NAME;
                outName.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_STR, 100, 0, 0, 1252);
            }
            if ((bool)_cpAddColumnType.Value == true)
            {
                IDTSOutputColumn90 outType = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                outType.Name = ErrorOutputDescription.EOD_COLUMN_TYPE;
                outType.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_STR, 100, 0, 0, 1252);
            }
            if ((bool)_cpAddColumnLength.Value == true)
            {
                IDTSOutputColumn90 outLength = ComponentMetaData.OutputCollection[0].OutputColumnCollection.New();
                outLength.Name = ErrorOutputDescription.EOD_COLUMN_LENGTH;
                outLength.SetDataTypeProperties(Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I4, 0, 0, 0, 0);
            }
        }

        /*
         * When a new path is attached to the component, we need to :
         *   - Rename input
         *   - Retrieve and store all virtual columns of the input
         *   - Create a new available input to allow user to attached another Error Output.
         */
        public override void OnInputPathAttached(int inputID)
        {
            try
            {
                // Call base method
                base.OnInputPathAttached(inputID);

                IDTSInput90 input = ComponentMetaData.InputCollection.GetObjectByID(inputID);
                IDTSVirtualInput90 vInput = input.GetVirtualInput();
                input.Name = "Error Input " + input.ID; // Rename with Unique name
                input.Dangling = false; // Can not be unattached
 
                // Retrieve and store Column Information
                foreach (IDTSVirtualInputColumn90 vCol in vInput.VirtualInputColumnCollection)
                {
                    VirtualInputColumn90 newInputColumn = new VirtualInputColumn90(input, vCol);
                    _VirtualInputColumns.GiveUniqueAlias(newInputColumn);
                    _VirtualInputColumns.Collection.Add(newInputColumn);
                    // Set Usage = 'ReadOnly' pour être prévenu en cas d'anomalie sur ces colonnes
                    SetUsageType(inputID, vInput, vCol.LineageID, DTSUsageType.UT_READONLY);
                }
                _VirtualInputColumns.Save(); // Store information in custom properties

                // Create another Input if user want to attached another component
                IDTSInput90 nextInput = ComponentMetaData.InputCollection.New();
                nextInput.Dangling = true; // Can be unattached
                nextInput.Name = "Error Input"; // Temporary Name
            }
            catch (Exception ex)
            {
                ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "OnInputPathAttached : " + ex.Message, "", 0);
            }
        }

        /*
         *  When a path is detached, associated objects are deleted.
         *  For each virtual column in the input : 
         *    - Remove column information
         *    - If the column was redirected to output, when this method removes output column.
         */
        public override void OnInputPathDetached(int inputID)
        {
            try
            {
                IDTSOutput90 output = ComponentMetaData.OutputCollection[0];
                for (int i = 0; i < _VirtualInputColumns.Collection.Count; i++)
                {
                    VirtualInputColumn90 vicColumn = (VirtualInputColumn90)_VirtualInputColumns.Collection[i];
                    if (vicColumn.Input_ID == inputID)
                    {
                        if (vicColumn.OutputColumn_IsUsed == true)
                            output.OutputColumnCollection.RemoveObjectByID(vicColumn.OutputColumn_ID);
                        _VirtualInputColumns.Collection.Remove(vicColumn);
                        i--;
                    }
                }
                _VirtualInputColumns.Save(); // Store information in custom properties

                // Call base method after our processing.
                base.OnInputPathDetached(inputID);
                // Finally, removes input.
                ComponentMetaData.InputCollection.RemoveObjectByID(inputID);
            }
            catch (Exception ex)
            {
                ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "OnInputPathDetached : " + ex.Message, "", 0);
            }
        }

        /*******************************************************************************************/
        /* DESIGN & RUNNING TIME : The following methods are called during design and running time */
        /*     - Validate() : Check if the component is valid                                      */
        /*     - ValidateInputs() : Check if inputs are valid                                      */
        /*     - ValidateErrorColumn() : Check if inputs contain ErrorCode and ErrorColumn columns */
        /*     - ReinitializeMetaData() : Reinitialize metadata when validation fails              */
        /*******************************************************************************************/

        /*
         * Validates the component configuration : This method is called each time a modification
         * is done in the current dataflow.
         * 
         * To be valid, the component must verify the following criterion :
         *  - At least one Input
         *  - One Output
         *  - LineageID property must contains a least one element. 
         *    User interface need to be open at least one time to initialize this property.
         * 
         */
        public override DTSValidationStatus Validate()
        {
            bool oCancel;

            // Put a warning if Custom Property tell so
            if ((bool)ComponentMetaData.CustomPropertyCollection[PROP_SHOW_WARNING].Value == true)
            {
                ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "Upstream components have changed, edit the component to refresh column information.", "", 0);
                return DTSValidationStatus.VS_ISBROKEN;
            }
            // Retrieve virtual input columns from our custom property
            if (_VirtualInputColumns == null || _VirtualInputColumns.Collection == null)
                _VirtualInputColumns = new VirtualInputColumns90(ComponentMetaData);

            // Check that there is one input.
            if (ComponentMetaData.InputCollection.Count <= 1)
            {
                ComponentMetaData.FireError(0, ComponentMetaData.Name, "You need to connect at least one input.", "", 0, out oCancel);
                return DTSValidationStatus.VS_ISBROKEN;
            }
            // Check that inputs contain ErrorCode and ErrorColumn columns
            if (!ValidateErrorColumn())
            {
                ComponentMetaData.FireError(0, ComponentMetaData.Name, "Input doesn't contain ErrorCode and ErrorColumn columns, or their datatype is not DT_I4.", "", 0, out oCancel);
                return DTSValidationStatus.VS_ISBROKEN;
            }
            // Check if inputs have change since last save
            if (!ValidateInputs() || !ComponentMetaData.AreInputColumnsValid)
            {
                ReinitializeMetaData();
                // Lineage Information need to be updated, this can only by done by opening the "Edit" window.
                // So, we put a warning, to tell the user he need to open editor again to update information.
                ComponentMetaData.CustomPropertyCollection[PROP_SHOW_WARNING].Value = true;
            }
            // Check that there is one output.
            if (   ComponentMetaData.OutputCollection.Count != 1 
                || ComponentMetaData.OutputCollection[0].OutputColumnCollection.Count <= 0)                
            {
                ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "Output contains 0 column, edit the component to configure output.", "", 0);
                return DTSValidationStatus.VS_ISBROKEN;
            }
            // Check that the Input Lineage ID custom property is set.
            if (ComponentMetaData.CustomPropertyCollection[PROP_LINEAGE_ID].Value == null || ((string)ComponentMetaData.CustomPropertyCollection[PROP_LINEAGE_ID].Value).Length == 0)
            {
                ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "Input Lineage ID property is not set. You need to edit the component.", "", 0);
                return DTSValidationStatus.VS_ISBROKEN;
            }
            // Call base method
            return base.Validate();
        }

        /*
         * Check if inputs have change since last Validate() call.
         * Return true if changes have been identified, false otherwise.
         */
        private bool ValidateInputs()
        {
            // First check : Compare the number of columns
            int nbVirtualInputColumns = 0;
            foreach (IDTSInput90 input in ComponentMetaData.InputCollection)
            {
                IDTSVirtualInput90 vInput = input.GetVirtualInput();
                nbVirtualInputColumns += vInput.VirtualInputColumnCollection.Count;
            }
            if (nbVirtualInputColumns != _VirtualInputColumns.Collection.Count)
            {
                ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "The number of available columns has changed.", "", 0);
                return false; // Columns have been added or deleted
            }

            // Second check : Compare columns information
            try
            {
                foreach (VirtualInputColumn90 vicCol in _VirtualInputColumns.Collection)
                {
                    IDTSInput90 input = ComponentMetaData.InputCollection.FindObjectByID(vicCol.Input_ID);
                    IDTSVirtualInput90 vInput = input.GetVirtualInput();
                    IDTSVirtualInputColumn90 vCol = vInput.VirtualInputColumnCollection.GetVirtualInputColumnByLineageID(vicCol.InputColumn_LineageID);
                    if (vCol == null)
                        throw new ApplicationException("The column " + vicCol.Input_Name + " has been deleted.");
                    if (!vCol.Name.Equals(vicCol.InputColumn_Name))
                        throw new ApplicationException("The column " + vicCol.Input_Name + " has been renamed.");
                    if ((int)vCol.DataType != vicCol.DataType)
                        throw new ApplicationException("The column " + vicCol.Input_Name + "'s datatype has changed.");
                    if (vCol.Length != vicCol.Length)
                        throw new ApplicationException("The column " + vicCol.Input_Name + "'s length has changed.");
                }
            }
            catch (Exception ex)
            {
                ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "Modification have been detected in input columns : " + ex.Message, "", 0);
                return false;
            }
            return true;
        }

        /*
         * Return false if an attached input does not contain ErrorCode and ErrorColumn columns.
         * User can connect a standard flow to this component but it need to provide these two error columns.
         */
        private bool ValidateErrorColumn()
        {
            bool bValide = false;
            bool bErrorCodeExist, bErrorColumnExist;

            foreach (IDTSInput90 input in ComponentMetaData.InputCollection)
            {
                if (input.IsAttached == false) // If this input is not attached, no error needs to be display.
                    bValide = true;
                else
                {   // If this input is attached, we check if ErrorCode and ErrorColumn columns exist.
                    IDTSVirtualInput90 vInput = input.GetVirtualInput();
                    bErrorCodeExist = false;
                    bErrorColumnExist = false;
                    foreach (IDTSVirtualInputColumn90 vCol in vInput.VirtualInputColumnCollection)
                    {
                        if (vCol.Name.Equals("ErrorCode") && vCol.DataType == Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I4)
                            bErrorCodeExist = true;
                        else if (vCol.Name.Equals("ErrorColumn") && vCol.DataType == Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I4)
                            bErrorColumnExist = true;
                        if (bErrorCodeExist && bErrorColumnExist)
                            break;
                    }
                    bValide = bErrorCodeExist && bErrorColumnExist;
                }
                if (bValide == false)
                    break;
            }
            return bValide;
        }

        /*
         * This method is called when changes have been detected in inputs columns.
         * This method reintializes the component depending on the detected changes :
         *    - Add new columns
         *    - Remove deleted columns
         *    - Modify existing columns whose description has been changed.
         */
        public override void ReinitializeMetaData()
        {
            // First step : Modify and Remove columns
            IDTSOutput90 output = ComponentMetaData.OutputCollection[0];
            for (int i = 0; i < _VirtualInputColumns.Collection.Count; i++)
            {
                VirtualInputColumn90 vicCol = (VirtualInputColumn90)_VirtualInputColumns.Collection[i];
                try
                {
                    IDTSInput90 input = ComponentMetaData.InputCollection.FindObjectByID(vicCol.Input_ID);
                    IDTSVirtualInput90 vInput = input.GetVirtualInput();
                    IDTSVirtualInputColumn90 vCol = vInput.VirtualInputColumnCollection.GetVirtualInputColumnByLineageID(vicCol.InputColumn_LineageID);
                    if (vCol == null) // Column has been deleted
                        throw new ApplicationException("The column " + vicCol.Input_Name + " has been deleted.");

                    // Reinitialize input information (ID, Name, etc...)
                    vicCol.ReInitializeInput(input, vCol);

                    // If DataType has changed, we need to change our Ouput if it exists
                    if (!vicCol.IsSameDataType(vCol))
                    {
                        if (vicCol.OutputColumn_IsUsed == true)
                        {
                            IDTSOutputColumn90 outCol = output.OutputColumnCollection.GetOutputColumnByLineageID(vicCol.OutputColumn_LineageID);
                            outCol.SetDataTypeProperties(vCol.DataType, vCol.Length, vCol.Precision, vCol.Scale, vCol.CodePage);
                        }
                        // Reinitialize Data Type information
                        vicCol.ReInitializeDataType(vCol);
                    }
                }
                catch (Exception)
                {
                    // Column has been removed, you need to delete all references to this column
                    ComponentMetaData.FireWarning(0, ComponentMetaData.Name, "Modification have been detected in input columns : The column " + vicCol.InputColumn_Name + " has been deleted.", "", 0);
                    if (vicCol.OutputColumn_IsUsed == true)
                        output.OutputColumnCollection.RemoveObjectByID(vicCol.OutputColumn_ID);
                    _VirtualInputColumns.Collection.Remove(vicCol);
                    i--;
                }
            }

            // Second step : Add new columns
            foreach (IDTSInput90 input in ComponentMetaData.InputCollection)
            {
                IDTSVirtualInput90 vInput = input.GetVirtualInput();
                foreach (IDTSVirtualInputColumn90 vCol in vInput.VirtualInputColumnCollection)
                {
                    bool bFound = false;
                    for (int i = 0; i < _VirtualInputColumns.Collection.Count; i++)
                    {
                        VirtualInputColumn90 vicColumn = (VirtualInputColumn90)_VirtualInputColumns.Collection[i];
                        if (vicColumn.Input_ID == input.ID && vicColumn.InputColumn_LineageID == vCol.LineageID)
                        {
                            bFound = true;
                            break;
                        }
                    }
                    if (bFound == false) // The column has not be found, it's a new column.
                    {
                        VirtualInputColumn90 newInputColumn = new VirtualInputColumn90(input, vCol);
                        _VirtualInputColumns.GiveUniqueAlias(newInputColumn);
                        _VirtualInputColumns.Collection.Add(newInputColumn);
                        // Set Usage = 'ReadOnly' pour être prévenu en cas d'anomalie sur ces colonnes
                        SetUsageType(input.ID, vInput, vCol.LineageID, DTSUsageType.UT_READONLY);
                    }
                }
            }
            _VirtualInputColumns.Save(); // Store information in custom properties

            // Call base method
            base.ReinitializeMetaData();
        }

        /*******************************************************************************************/
        /* RUNNING TIME : The following methods are called during running time                     */
        /*     - PreExecute() : Initializes some variables before execution                        */
        /*     - PrimeOutput() : Store output buffer                                               */
        /*     - ProcessInput() : Read input buffer and write data in output buffer                */
        /*     - PostExecute() : Is called when execution is finished                              */
        /*******************************************************************************************/

        /*
         * Initialize internal objects before execution :
         *   - Count number of inputs
         *   - Retrieve custom properties data
         *   - Store input buffer indexes
         *   - Store output buffer indexes
         */
        public override void PreExecute()
        {
            // Call base methode
            base.PreExecute();

            // Initializes number of inputs to determine when output flow must be closed
            _iNbActiveInput = ComponentMetaData.InputCollection.Count - 1;

            // Initializes the generated rows's counter
            _iNbGeneratedRows = 0;

            // Get custom properties
            _cpLineage             = ComponentMetaData.CustomPropertyCollection[PROP_LINEAGE_ID];
            _cpAddErrorCode        = ComponentMetaData.CustomPropertyCollection[PROP_ERROR_CODE];
            _cpAddErrorColumn      = ComponentMetaData.CustomPropertyCollection[PROP_ERROR_COLUMN];
            _cpAddErrorDescription = ComponentMetaData.CustomPropertyCollection[PROP_ERROR_DESCRIPTION];
            _cpAddComponentName    = ComponentMetaData.CustomPropertyCollection[PROP_COMPONENT_NAME];
            _cpAddColumnName       = ComponentMetaData.CustomPropertyCollection[PROP_COLUMN_NAME];
            _cpAddColumnType       = ComponentMetaData.CustomPropertyCollection[PROP_COLUMN_TYPE];
            _cpAddColumnLength     = ComponentMetaData.CustomPropertyCollection[PROP_COLUMN_LENGTH];

            // Retrieve and format all columns information from our custom property (Lineage ID)
            try
            {
                String strLineage = (String)_cpLineage.Value;
                String[] tabLineage = strLineage.Split(';');
                _ciColumnInfo = new ColumnInfo90[tabLineage.Length - 1];

                for (int i = 0; i < tabLineage.Length - 1; i++)
                {
                    String[] tabDetail = tabLineage[i].Split(':');
                    _ciColumnInfo[i] = new ColumnInfo90();
                    _ciColumnInfo[i].LineageID = Int32.Parse(tabDetail[0]);
                    _ciColumnInfo[i].Name      = tabDetail[1];
                    _ciColumnInfo[i].DataType  = tabDetail[2];
                    _ciColumnInfo[i].Length    = Int32.Parse(tabDetail[3]);
                }
            }
            catch(Exception ex)
            {
                bool oCancel;
                ComponentMetaData.FireError(0, ComponentMetaData.Name, "Bad '" + PROP_LINEAGE_ID + "' property, component's interface need to be open to initialize this property.", "", 0, out oCancel);
                ComponentMetaData.FireError(0, ComponentMetaData.Name, ex.Message, "", 0, out oCancel);
                ComponentMetaData.FireError(0, ComponentMetaData.Name, ex.StackTrace, "", 0, out oCancel);
                throw new ApplicationException("Bad '" + PROP_LINEAGE_ID + "' property.");
            }
 
            // Retrieve and Store position of columns in input buffers.
            //   - Get ErrorCode and ErrorColumn columns positions
            //   - Get all input columns positions
            _ErrorCodeIndex   = new int[ComponentMetaData.InputCollection.Count];
            _ErrorColumnIndex = new int[ComponentMetaData.InputCollection.Count];
            int iErrorCodeIndex, iErrorColumnIndex;

            for (int i = 0; i < ComponentMetaData.InputCollection.Count; i++)
            {
                IDTSInput90 input = ComponentMetaData.InputCollection[i];
                IDTSVirtualInput90 vInput = input.GetVirtualInput();
                iErrorCodeIndex = -1;
                iErrorColumnIndex = -1;
                for (int col = 0; col < vInput.VirtualInputColumnCollection.Count; col++)
                {
                    IDTSVirtualInputColumn90 vCol = vInput.VirtualInputColumnCollection[col];
                    int iColumnIndex = BufferManager.FindColumnByLineageID(input.Buffer, vCol.LineageID);
                    if (vCol.Name.Equals("ErrorCode"))
                        iErrorCodeIndex = iColumnIndex;
                    else if (vCol.Name.Equals("ErrorColumn"))
                        iErrorColumnIndex = iColumnIndex;

                    for (int j = 0; i < _VirtualInputColumns.Collection.Count; j++)
                    {
                        VirtualInputColumn90 vicCol = (VirtualInputColumn90)_VirtualInputColumns.Collection[j];
                        if (input.ID == vicCol.Input_ID && vCol.LineageID == vicCol.InputColumn_LineageID)
                        {
                            vicCol.InputBufferIndex = iColumnIndex;
                            break;
                        }
                    }
                }
                _ErrorCodeIndex[i] = iErrorCodeIndex;
                _ErrorColumnIndex[i] = iErrorColumnIndex;
            }

            // Retrieve and Store position of columns in output buffers.
            //   - Get our component's specific columns positions (Error Description, Error Name, etc...)
            //   - Get all redirected columns positions
            IDTSOutput90 output = ComponentMetaData.OutputCollection[0];
            _iErrorCodeIndex = -1;
            _iErrorColumnIndex = -1;
            _iErrorDescriptionIndex = -1;
            _iComponentNameIndex = -1;
            _iColumnNameIndex = -1;
            _iColumnTypeIndex = -1;
            _iColumnLengthIndex = -1;
            for (int col = 0; col < output.OutputColumnCollection.Count; col++)
            {
                IDTSOutputColumn90 outputColumn = ComponentMetaData.OutputCollection[0].OutputColumnCollection[col];
                int iColumnIndex = BufferManager.FindColumnByLineageID(output.Buffer, outputColumn.LineageID);
                if (outputColumn.Name.Equals(EOD_ERROR_CODE))
                    _iErrorCodeIndex        = iColumnIndex;
                else if (outputColumn.Name.Equals(EOD_ERROR_COLUMN))
                    _iErrorColumnIndex      = iColumnIndex;
                else if (outputColumn.Name.Equals(EOD_ERROR_DESCRIPTION))
                    _iErrorDescriptionIndex = iColumnIndex;
                else if (outputColumn.Name.Equals(EOD_COMPONENT_NAME))
                    _iComponentNameIndex    = iColumnIndex;
                else if (outputColumn.Name.Equals(EOD_COLUMN_NAME))
                    _iColumnNameIndex       = iColumnIndex;
                else if (outputColumn.Name.Equals(EOD_COLUMN_TYPE))
                    _iColumnTypeIndex       = iColumnIndex;
                else if (outputColumn.Name.Equals(EOD_COLUMN_LENGTH))
                    _iColumnLengthIndex = iColumnIndex;
                else
                    for (int j = 0; j < _VirtualInputColumns.Collection.Count; j++)
                    {
                        VirtualInputColumn90 vicCol = (VirtualInputColumn90)_VirtualInputColumns.Collection[j];
                        if (outputColumn.LineageID == vicCol.OutputColumn_LineageID)
                        {
                            vicCol.OutputBufferIndex = iColumnIndex;
                            break;
                        }
                    }
            }
        }

        /*
         * Store our output buffer. This buffer will be used during ProcessInput method.
         */
        public override void PrimeOutput(int outputs, int[] outputIDs, PipelineBuffer[] buffers)
        {
            if (buffers.Length != 0)
                _outputBuffer = buffers[0]; // We have only one output
        }

        /*
         * During running time this method goes through all lines written by
         * upstream components and generates lines for the downstream component.
         * 
         * For each input, for each input row :
         *   - Read error code, determine error description and write it to output.
         *   - Read error column, determine column information and write it to output.
         *   - Read input column to redirect and write it to output.
         */
        public override void ProcessInput(int inputID, PipelineBuffer buffer)
        {
            bool oCancel;

            try
            {
                // Determines which input generated the error
                int iInputIndex = ComponentMetaData.InputCollection.GetObjectIndexByID(inputID);
                if (iInputIndex == -1)
                    throw new ApplicationException("Invalid input index");

                int iErrorCodeIndex   = _ErrorCodeIndex[iInputIndex];
                int iErrorColumnIndex = _ErrorColumnIndex[iInputIndex];
                if (iErrorCodeIndex == -1 || iErrorColumnIndex == -1)
                    throw new ApplicationException("Invalid error column index");

                // Loop for each row in the buffer
                while (buffer.NextRow())
                {
                    // Add a row to the output buffer.
                    _outputBuffer.AddRow();
                    _iNbGeneratedRows++;

                    
                    if (!buffer.IsNull(iErrorCodeIndex))
                    {
                        int iErrorCode = buffer.GetInt32(iErrorCodeIndex); // Read ErrorCode from Input Buffer

                        // Step 1 : Write Error Code if needed
                        if ((bool)_cpAddErrorCode.Value == true && _iErrorCodeIndex != -1)
                            _outputBuffer.SetInt32(_iErrorCodeIndex, iErrorCode);

                        // Step 2 : Write Error Description if needed
                        if ((bool)_cpAddErrorDescription.Value == true && _iErrorDescriptionIndex != -1)
                        {
                            string strDescription;
                            try
                            {
                                strDescription = ComponentMetaData.GetErrorDescription(iErrorCode); // Get description for this error code
                            }
                            catch (Exception)
                            {
                                strDescription = "Error inconnue";
                            }
                            _outputBuffer.SetString(_iErrorDescriptionIndex, strDescription); // Write description to Output Buffer
                        }
                    }
                    
                    // Write Error Column information and Component Name if needed
                    if (!buffer.IsNull(iErrorColumnIndex))
                    {
                        // Read ErrorColumn from Input Buffer.
                        // This column contains LineageID of the column which generated the error.
                        int iColLineageID = buffer.GetInt32(iErrorColumnIndex);

                        // Step 3 : Write Error Column if needed
                        if ((bool)_cpAddErrorColumn.Value == true && _iErrorColumnIndex != -1)
                            _outputBuffer.SetInt32(_iErrorColumnIndex, iColLineageID);

                        if (   ((bool)_cpAddColumnName.Value == true   && _iColumnNameIndex != -1)
                            || ((bool)_cpAddColumnType.Value == true   && _iColumnTypeIndex != -1)
                            || ((bool)_cpAddColumnLength.Value == true && _iColumnLengthIndex != -1))
                        {
                            int j;

                            if (iColLineageID == 0) // Error impacts the row and not a specific column
                            {
                                if ((bool)_cpAddColumnName.Value == true && _iColumnNameIndex != -1)
                                    _outputBuffer.SetString(_iColumnNameIndex, "[NonColumnSpecificError]");
                            }
                            else // Error impacts a specific column
                            {
                                // Search column information
                                for (j = 0; j < _ciColumnInfo.Length; j++)
                                    if (_ciColumnInfo[j].LineageID == iColLineageID)
                                        break;
                                if (j < _ciColumnInfo.Length) // Column information has been found
                                {
                                    // Step 4 : Write Column Name to Output Buffer if needed
                                    if ((bool)_cpAddColumnName.Value == true && _iColumnNameIndex != -1)
                                        _outputBuffer.SetString(_iColumnNameIndex, _ciColumnInfo[j].Name);
                                    // Step 5 : Write Column Type to Output Buffer if needed
                                    if ((bool)_cpAddColumnType.Value == true && _iColumnTypeIndex != -1)
                                        _outputBuffer.SetString(_iColumnTypeIndex, _ciColumnInfo[j].DataType);
                                    // Step 6 : Write Column Length to Output Buffer if needed
                                    if ((bool)_cpAddColumnLength.Value == true && _iColumnLengthIndex != -1)
                                        _outputBuffer.SetInt32(_iColumnLengthIndex, _ciColumnInfo[j].Length);
                                }
                            }
                        }

                        // Step 7 : Write Component Name if needed
                        if ((bool)_cpAddComponentName.Value == true && _iComponentNameIndex != -1)
                            for (int j = 0; j < _VirtualInputColumns.Collection.Count; j++)
                            {
                                VirtualInputColumn90 vicCol = (VirtualInputColumn90)_VirtualInputColumns.Collection[j];
                                if (inputID == vicCol.Input_ID && vicCol.InputColumn_Name.Equals("ErrorCode"))
                                {
                                    // Write ErrorCode column's component name to Output Buffer
                                    _outputBuffer.SetString(_iComponentNameIndex, vicCol.InputColumn_UpstreamComponentName);
                                    break;
                                }
                            }
                    }
                    // Step 8 : Write inputs columns which need to be redirected to output
                    for (int j = 0; j < _VirtualInputColumns.Collection.Count; j++)
                    {
                        VirtualInputColumn90 vicCol = (VirtualInputColumn90)_VirtualInputColumns.Collection[j];
                        if (inputID == vicCol.Input_ID && vicCol.OutputColumn_IsUsed == true
                            && vicCol.OutputBufferIndex != -1 && vicCol.InputBufferIndex != -1)
                        {
                            // The method used to write data to Output Buffer depends on the data type
                            try
                            {
                                switch (vicCol.DataType)
                                {
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_IMAGE:
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_NTEXT:
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_TEXT:
                                        _outputBuffer.AddBlobData(vicCol.OutputBufferIndex, buffer.GetBlobData(vicCol.InputBufferIndex, 0, (int)buffer.GetBlobLength(vicCol.InputBufferIndex)));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_BOOL:
                                        _outputBuffer.SetBoolean(vicCol.OutputBufferIndex, buffer.GetBoolean(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_BYTES:
                                        _outputBuffer.SetByte(vicCol.OutputBufferIndex, buffer.GetByte(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_CY:
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_NUMERIC:
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_DECIMAL:
                                        _outputBuffer.SetDecimal(vicCol.OutputBufferIndex, buffer.GetDecimal(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_DATE:
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_DBTIMESTAMP:
                                        _outputBuffer.SetDateTime(vicCol.OutputBufferIndex, buffer.GetDateTime(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_GUID:
                                        _outputBuffer.SetGuid(vicCol.OutputBufferIndex, buffer.GetGuid(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I1:
                                        _outputBuffer.SetSByte(vicCol.OutputBufferIndex, buffer.GetSByte(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I2:
                                        _outputBuffer.SetInt16(vicCol.OutputBufferIndex, buffer.GetInt16(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I4:
                                        _outputBuffer.SetInt32(vicCol.OutputBufferIndex, buffer.GetInt32(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_I8:
                                        _outputBuffer.SetInt64(vicCol.OutputBufferIndex, buffer.GetInt64(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_R4:
                                        _outputBuffer.SetSingle(vicCol.OutputBufferIndex, buffer.GetSingle(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_R8:
                                        _outputBuffer.SetDouble(vicCol.OutputBufferIndex, buffer.GetDouble(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_STR:
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_WSTR:
                                        _outputBuffer.SetString(vicCol.OutputBufferIndex, buffer.GetString(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_UI1:
                                        _outputBuffer.SetByte(vicCol.OutputBufferIndex, buffer.GetByte(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_UI2:
                                        _outputBuffer.SetUInt16(vicCol.OutputBufferIndex, buffer.GetUInt16(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_UI4:
                                        _outputBuffer.SetUInt32(vicCol.OutputBufferIndex, buffer.GetUInt32(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_UI8:
                                        _outputBuffer.SetUInt64(vicCol.OutputBufferIndex, buffer.GetUInt64(vicCol.InputBufferIndex));
                                        break;
                                    case (int)Microsoft.SqlServer.Dts.Runtime.Wrapper.DataType.DT_NULL:
                                        _outputBuffer.SetNull(vicCol.OutputBufferIndex);
                                        break;
                                    default:
                                        _outputBuffer.SetNull(vicCol.OutputBufferIndex);
                                        break;
                                }
                            }
                            catch (Exception)
                            {
                                _outputBuffer.SetNull(vicCol.OutputBufferIndex);
                            }
                        }
                    }
                }

                // Manage end of data
                if (buffer.EndOfRowset == true) // This input is closed
                {
                    _iNbActiveInput--; // Decrement the number of active inputs
                    if (_iNbActiveInput <= 0) // If all inputs are closed, then we close output.
                        _outputBuffer.SetEndOfRowset();
                    return;
                }
            }
            catch (Exception ex)
            {
                ComponentMetaData.FireError(0, ComponentMetaData.Name, ex.Message, string.Empty, 0, out oCancel);
                ComponentMetaData.FireError(0, ComponentMetaData.Name, ex.StackTrace, string.Empty, 0, out oCancel);
                throw new ApplicationException("Could Not Process Input");
            }
        }

        /*
         * When execution is finished, this method is called and writes the number of lines generated to log.
         */
        public override void PostExecute()
        {
            bool oPbFireAgain = false;
            ComponentMetaData.FireInformation(0, ComponentMetaData.Name, "Component \"" + ComponentMetaData.Name + "\" wrote " + _iNbGeneratedRows.ToString() + " rows.", string.Empty, 0, ref oPbFireAgain);
            base.PostExecute();
        }
    }
}
